/**
 * Copyright 2010 The Regents of the University of California
 *
 *  All Rights Reserved
 */
/**
 *
 * File: downward-entries.c
 * For inserting the deterministic downward flow entries at the switch start-up time, Optimization for performance imprivement
 */


#include <config.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
#include <inttypes.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>

#include "dcswitch.h"
#include "flow.h"
#include "learning-switch.h"
#include "new-switch-handler.h"
#include "flow-insertion.h"
#include "ofpbuf.h"
#include "openflow/openflow.h"
#include "poll-loop.h"
#include "queue.h"
#include "rconn.h"
#include "timeval.h"
#include "vconn.h"
#include "xtoxll.h"
#include "arp-handling.h"

#define THIS_MODULE VLM_downward_entries
#include "vlog.h"

#define HOST_ID(x) ((((x) & 0x00FF0000)>>16) * 4 + (((x) & 0x0000FF00) >> 8) * 2 + ((x) & 0x000000FF) - 2)

//////////////////////////////////////////////////////////
// static function declarations
//////////////////////////////////////////////////////////


void init_switch_prefixes(int sw_index, struct lswitch* sw, struct rconn* rconn);
static void insert_prefix_in_edge(int sw_index, struct lswitch *sw, struct rconn* rconn);
static void insert_prefix_in_aggr(int sw_index, struct lswitch *sw, struct rconn* rconn);
static void insert_prefix_in_core(int sw_index, struct lswitch *sw, struct rconn* rconn);


//////////////////////////////////////////////////////////
// Function definitions
//////////////////////////////////////////////////////////


/**
 * This function inserts a new flow entry based on the logic
 * originally implemented in Portland
 *
 * The new flow entry is an exact-match entry and should be inserted into SRAM.
 *
 * @param 
 *
 */
#ifdef HEDERA
static int determine_port_to_aggr(int edge_index, uint32_t dstIP)
{
	DCSwitch_node *node = DCSwitches[edge_index];
	unsigned dstID = HOST_ID(dstIP);
	int aggr =dstID % (K/2);
	int i;
	int ct=0;
	int out_port;
	int index=0;
	VLOG_DBG("in aggr Dst id: %x, aggr: %d ", dstID, aggr);
	
	for(i=0; i<K; i++)
	{
		if(node->ports[i][1]==1 && node->neighbours[i]!=NULL && ct<=aggr)  //find the upward port
		{
			ct++;	
			out_port = node->ports[i][0];
			index = i;
		}	
	}
	VLOG_DBG("Out port: %d Index: %d Neighbour switch id: %d",node->ports[index][0],index, (node->neighbours[index])->sw_indexTmp );
	return index;
		
}


static int determine_port_to_core(int aggr_index, uint32_t dstIP)
{
	DCSwitch_node *node = DCSwitches[aggr_index];
	unsigned dstID = HOST_ID(dstIP);
	int core = dstID % (K/2);
	int i, ct=0, out_port, index;
	VLOG_DBG("in core Dst id: %x ", dstID);
	for(i=0; i<K; i++)
	{
		if(node->ports[i][1]==1 && node->neighbours[i]!=NULL && ct<=core)  //find the upward port
		{
			ct++;	
			out_port = node->ports[i][0];
			index = i;
		}	
	}
	VLOG_DBG("Out port: %d Index: %d Neighbour switch id: %d",node->ports[index][0],index, (node->neighbours[index])->sw_indexTmp );
	return index;
}

#endif

void init_switch_prefixes_get_downward(int index, struct lswitch* sw, struct rconn* rconn)
{
	int fattree_index; 
	DCSwitch_node* node = DCSwitches[index];
	if (node == NULL)
		return;
	VLOG_DBG("Initialzing prefix for switch index %d fattree_index %d ", node->index, node->fattree_index );

	fattree_index = node->fattree_index;
	
	if(fattree_index >= K*K && fattree_index < 5*K*K/4 && getInsertDownwardFlowEntries())
		insert_prefix_in_core(index, sw, rconn);
	else if((fattree_index)%K >= K/2 && getInsertDownwardFlowEntries())
			insert_prefix_in_aggr(index, sw, rconn);
	else if((fattree_index)%K < K/2 && fattree_index >= 0)
		insert_prefix_in_edge(index, sw, rconn);
	else VLOG_DBG("Error weird switch index ft %d index %d", fattree_index, index);

}

void init_switch_prefixes(int index, struct lswitch* sw, struct rconn* rconn)
{
	int fattree_index; 
	DCSwitch_node* node = DCSwitches[index];
	if (node == NULL)
		return;

	VLOG_DBG("Initialzing prefix for switch index %d fattree_index %d ", node->index, node->fattree_index );
	fattree_index = node->fattree_index;
	
	if(fattree_index >= K*K && fattree_index < 5*K*K/4 && getInsertDownwardFlowEntries())
		insert_prefix_in_core(index, sw, rconn);
	else if((fattree_index)%K >= K/2  && getInsertDownwardFlowEntries())
			insert_prefix_in_aggr(index, sw, rconn);
	else if((fattree_index)%K < K/2 && fattree_index >= 0)
		insert_prefix_in_edge(index, sw, rconn);
	else VLOG_DBG("Error weird switch index ft %d index %d", fattree_index, index);

}

static void insert_prefix_in_edge(int index, struct lswitch* sw, struct rconn* rconn)
{
	int i;
	uint32_t dstIP;
	DCSwitch_node* node = DCSwitches[index];
	int pod = node->pod;
	int position = (node->fattree_index)%(K/2);
	uint8_t dstPMAC[ETH_ADDR_LEN];

	FILE *fp = fopen("temp", "a");

	VLOG_DBG("Initialzing prefix for edge switch %d fattree_index %d ", node->index, node->fattree_index);
	VLOG_DBG("inserting entry in edge %d %d. No. of ports: %d", node->fattree_index,  node->index, node->n_ports);
	for(i=0; i<K; i++)
	{
		if(node->ports[i][1] == 0  && node->ports[i][0]!=-1) //not upward facing ports
		{
			#ifdef VM
			dstPMAC[0] = 0;
			dstPMAC[1] = 1;
			dstPMAC[2] = pod;
			dstPMAC[3] = position;
			dstPMAC[4] = i;
			dstPMAC[5] = last_vm_id[index][i];
			#else
			dstPMAC[0] = 0;
			dstPMAC[1] = 1;
			dstPMAC[2] = 1;
			dstPMAC[3] = pod;
			dstPMAC[4] = position;
			dstPMAC[5] = i;
			#endif	
		
			dstIP = convert_to_uint_ip(dstPMAC);
			VLOG_DBG( "Port: %d\t PMAC: %x:%x:%x:%x:%x:%x\t PIP: 0x%.8x 0x%.8x\n",  node->ports[i][0], dstPMAC[0], dstPMAC[1],dstPMAC[2], dstPMAC[3], dstPMAC[4], dstPMAC[5],  dstIP, htonl(dstIP));
			if(fp)
	          	fprintf(fp, "%d\t%x\t%x\t%x\t%x\t0x%.8x\t%.2x:%.2x:%.2x:%.2x\n",  node->ports[i][0], dstPMAC[2], dstPMAC[3], dstPMAC[4], dstPMAC[5], convert_to_uint_ip(dstPMAC), dstPMAC[2], dstPMAC[3], dstPMAC[4], dstPMAC[5]);

/*
			if(getInsertDownwardFlowEntries() && getFlowEntryMatching() == PMAC)
				insert_L2_entry(rconn, sw, dstPMAC, node->ports[i][0]);
			else if(getInsertDownwardFlowEntries() && getFlowEntryMatching() == PIP)
				insert_L3_entry(rconn, sw, dstIP, node->ports[i][0]);
*/			
			
		}
		else
			VLOG_DBG("Switch %d fattree_index %d port %d is facing %d", node->index, node->fattree_index, node->ports[i][0], node->ports[i][1]);
		
	}	
	if(fp)
		fclose(fp);
	else
		VLOG_DBG("Could not open the file to write Portland Psuedo addresses. Error: %d %s\n", errno, strerror(errno));

}

static void insert_prefix_in_aggr(int index, struct lswitch* sw, struct rconn* rconn)
{
	int i,j;
	DCSwitch_node* node = DCSwitches[index];
	int pod = node->pod;
	uint8_t dstPMAC[ETH_ADDR_LEN];
	int position;
	uint32_t dstPIP;
	VLOG_DBG("Initialzing prefix for aggr switch %d fattree_index %d", node->index,  node->fattree_index);

	
	for(i=0; i<K; i++)
	{
		if(node->ports[i][1] == 0)
		{
			for(j=0;j<K;j++)
			{
			if(node->neighbours[i]!=NULL)
			{
				position = ((node->neighbours[i])->fattree_index) % (K/2);
				if((node->ports[i][1]) == 0 && (node->neighbours[i])->ports[j][1]==0) //downward facing ports
				{
					#ifdef VM
					dstPMAC[0] = 0;
					dstPMAC[1] = 1;
					dstPMAC[2] = pod;
					dstPMAC[3] = position;
					dstPMAC[4] = j;
					dstPMAC[5] = last_vm_id[index][j];
					#else
					dstPMAC[0] = 0;
					dstPMAC[1] = 1;
					dstPMAC[2] = 1;
					dstPMAC[3] = pod;
					dstPMAC[4] = position;
					dstPMAC[5] = j;
					#endif
					VLOG_DBG("Inserting entry for aggr switch %d. Switch %d %d %d", (node->neighbours[i])->fattree_index,(node->neighbours[i])->sw_indexTmp, ((node->neighbours[i])->fattree_index)%(K/2), position);
					dstPIP = convert_to_uint_ip(dstPMAC);

					if(getFlowEntryMatching() == PMAC && getInsertDownwardFlowEntries())
						insert_L2_entry(rconn, sw, dstPMAC, node->ports[i][0]);
					else if(getFlowEntryMatching() == PIP && getInsertDownwardFlowEntries())
						insert_L3_entry(rconn, sw, dstPIP, node->ports[i][0]);

				}
			}
			}
		}
	}
}

static void insert_prefix_in_core(int index, struct lswitch *sw, struct rconn* rconn)
{
	int i,j,k,pod, position;
	uint8_t dstPMAC[ETH_ADDR_LEN];
	DCSwitch_node* aggr_node;
	DCSwitch_node* node = DCSwitches[index];
	uint32_t dstPIP;
	VLOG_DBG("Initialzing prefix for core switch %d fattree_index %d", node->index, node->fattree_index);
	
	for(i=0;i<K;i++)
	{	
		for(j=0;j<K;j++)
		{
			if(node->neighbours[i]!=NULL)
			{
		    	aggr_node = node->neighbours[i];		
				if(aggr_node->ports[j][1] == 0 && aggr_node->neighbours[j]!=NULL)
				for(k=0;k<K;k++)
				{
					if((aggr_node->neighbours[j])->ports[k][1] == 0 ) //downward facing ports
					{
						pod = aggr_node->pod;
						position = ((aggr_node->neighbours[j])->fattree_index) % (K/2);
						#ifdef VM
						dstPMAC[0] = 0;
						dstPMAC[1] = 1;
						dstPMAC[2] = pod;
						dstPMAC[3] = position;
						dstPMAC[4] = k;
						dstPMAC[5] = last_vm_id[index][k];
						#else
						dstPMAC[0] = 0;
						dstPMAC[1] = 1;
						dstPMAC[2] = 1;
						dstPMAC[3] = pod;
						dstPMAC[4] = position;
						dstPMAC[5] = k;
						#endif
			//			fprintf(stdout, "%d\t%d\t0x%.8x\t%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", node->sw_indexTmp, node->ports[i][0], convert_to_uint_ip(dstPMAC), dstPMAC[0],dstPMAC[1], dstPMAC[2], dstPMAC[3], dstPMAC[4], dstPMAC[5]) ;

						if(getFlowEntryMatching() == PMAC && getInsertDownwardFlowEntries())
							insert_L2_entry(rconn, sw, dstPMAC, node->ports[i][0]);
						else if(getFlowEntryMatching() == PIP && getInsertDownwardFlowEntries())
						{
							dstPIP = convert_to_uint_ip(dstPMAC);
							insert_L3_entry(rconn, sw, dstPIP, node->ports[i][0]);
						}
					}
				}
			}
		}
	}
}	
